// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

package com.google.protobuf;

import java.io.IOException;
import java.util.AbstractMap;
import java.util.Map;

/**
 * Implements the lite version of map entry messages.
 *
 * <p>This class serves as an utility class to help do serialization/parsing of map entries. It's
 * used in generated code and also in the full version MapEntry message.
 *
 * <p>Protobuf internal. Users shouldn't use.
 */
public class MapEntryLite<K, V> {

    static class Metadata<K, V> {
        public final WireFormat.FieldType keyType;
        public final K defaultKey;
        public final WireFormat.FieldType valueType;
        public final V defaultValue;

        public Metadata(
                WireFormat.FieldType keyType,
                K defaultKey,
                WireFormat.FieldType valueType,
                V defaultValue) {
            this.keyType = keyType;
            this.defaultKey = defaultKey;
            this.valueType = valueType;
            this.defaultValue = defaultValue;
        }
    }

    private static final int KEY_FIELD_NUMBER = 1;
    private static final int VALUE_FIELD_NUMBER = 2;

    private final Metadata<K, V> metadata;
    private final K key;
    private final V value;

    /**
     * Creates a default MapEntryLite message instance.
     */
    private MapEntryLite(
            WireFormat.FieldType keyType, K defaultKey, WireFormat.FieldType valueType, V defaultValue) {
        this.metadata = new Metadata<K, V>(keyType, defaultKey, valueType, defaultValue);
        this.key = defaultKey;
        this.value = defaultValue;
    }

    /**
     * Creates a new MapEntryLite message.
     */
    private MapEntryLite(Metadata<K, V> metadata, K key, V value) {
        this.metadata = metadata;
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }

    /**
     * Creates a default MapEntryLite message instance.
     *
     * <p>This method is used by generated code to create the default instance for a map entry
     * message. The created default instance should be used to create new map entry messages of the
     * same type. For each map entry message, only one default instance should be created.
     */
    public static <K, V> MapEntryLite<K, V> newDefaultInstance(
            WireFormat.FieldType keyType, K defaultKey, WireFormat.FieldType valueType, V defaultValue) {
        return new MapEntryLite<K, V>(keyType, defaultKey, valueType, defaultValue);
    }

    static <K, V> void writeTo(CodedOutputStream output, Metadata<K, V> metadata, K key, V value)
            throws IOException {
        FieldSet.writeElement(output, metadata.keyType, KEY_FIELD_NUMBER, key);
        FieldSet.writeElement(output, metadata.valueType, VALUE_FIELD_NUMBER, value);
    }

    static <K, V> int computeSerializedSize(Metadata<K, V> metadata, K key, V value) {
        return FieldSet.computeElementSize(metadata.keyType, KEY_FIELD_NUMBER, key)
                + FieldSet.computeElementSize(metadata.valueType, VALUE_FIELD_NUMBER, value);
    }

    @SuppressWarnings("unchecked")
    static <T> T parseField(
            CodedInputStream input,
            ExtensionRegistryLite extensionRegistry,
            WireFormat.FieldType type,
            T value)
            throws IOException {
        switch (type) {
            case MESSAGE:
                MessageLite.Builder subBuilder = ((MessageLite) value).toBuilder();
                input.readMessage(subBuilder, extensionRegistry);
                return (T) subBuilder.buildPartial();
            case ENUM:
                return (T) (java.lang.Integer) input.readEnum();
            case GROUP:
                throw new RuntimeException("Groups are not allowed in maps.");
            default:
                return (T) FieldSet.readPrimitiveField(input, type, true);
        }
    }

    /**
     * Serializes the provided key and value as though they were wrapped by a {@link MapEntryLite} to
     * the output stream. This helper method avoids allocation of a {@link MapEntryLite} built with a
     * key and value and is called from generated code directly.
     */
    public void serializeTo(CodedOutputStream output, int fieldNumber, K key, V value)
            throws IOException {
        output.writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
        output.writeUInt32NoTag(computeSerializedSize(metadata, key, value));
        writeTo(output, metadata, key, value);
    }

    /**
     * Computes the message size for the provided key and value as though they were wrapped by a
     * {@link MapEntryLite}. This helper method avoids allocation of a {@link MapEntryLite} built with
     * a key and value and is called from generated code directly.
     */
    public int computeMessageSize(int fieldNumber, K key, V value) {
        return CodedOutputStream.computeTagSize(fieldNumber)
                + CodedOutputStream.computeLengthDelimitedFieldSize(
                computeSerializedSize(metadata, key, value));
    }

    /**
     * Parses an entry off of the input as a {@link Map.Entry}. This helper requires an allocation so
     * using {@link #parseInto} is preferred if possible.
     */
    public Map.Entry<K, V> parseEntry(ByteString bytes, ExtensionRegistryLite extensionRegistry)
            throws IOException {
        return parseEntry(bytes.newCodedInput(), metadata, extensionRegistry);
    }

    static <K, V> Map.Entry<K, V> parseEntry(
            CodedInputStream input, Metadata<K, V> metadata, ExtensionRegistryLite extensionRegistry)
            throws IOException {
        K key = metadata.defaultKey;
        V value = metadata.defaultValue;
        while (true) {
            int tag = input.readTag();
            if (tag == 0) {
                break;
            }
            if (tag == WireFormat.makeTag(KEY_FIELD_NUMBER, metadata.keyType.getWireType())) {
                key = parseField(input, extensionRegistry, metadata.keyType, key);
            } else if (tag == WireFormat.makeTag(VALUE_FIELD_NUMBER, metadata.valueType.getWireType())) {
                value = parseField(input, extensionRegistry, metadata.valueType, value);
            } else {
                if (!input.skipField(tag)) {
                    break;
                }
            }
        }
        return new AbstractMap.SimpleImmutableEntry<K, V>(key, value);
    }

    /**
     * Parses an entry off of the input into the map. This helper avoids allocation of a {@link
     * MapEntryLite} by parsing directly into the provided {@link MapFieldLite}.
     */
    public void parseInto(
            MapFieldLite<K, V> map, CodedInputStream input, ExtensionRegistryLite extensionRegistry)
            throws IOException {
        int length = input.readRawVarint32();
        final int oldLimit = input.pushLimit(length);
        K key = metadata.defaultKey;
        V value = metadata.defaultValue;

        while (true) {
            int tag = input.readTag();
            if (tag == 0) {
                break;
            }
            if (tag == WireFormat.makeTag(KEY_FIELD_NUMBER, metadata.keyType.getWireType())) {
                key = parseField(input, extensionRegistry, metadata.keyType, key);
            } else if (tag == WireFormat.makeTag(VALUE_FIELD_NUMBER, metadata.valueType.getWireType())) {
                value = parseField(input, extensionRegistry, metadata.valueType, value);
            } else {
                if (!input.skipField(tag)) {
                    break;
                }
            }
        }

        input.checkLastTagWas(0);
        input.popLimit(oldLimit);
        map.put(key, value);
    }

    /**
     * For experimental runtime internal use only.
     */
    Metadata<K, V> getMetadata() {
        return metadata;
    }
}
